package query;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import model.Balise;
import model.Document;
import model.Emplacement;
import model.Mot;
import query.model.QueryWord;
import query.model.UserQuery;
import utils.MapUtil;

/**
 * The {@link QueryUtility} finds relevent {@link Balise}s
 * for a specific {@link UserQuery}
 */
public class QueryUtility
{
  /** The documents inserted. */
  private List<Document> documentsInserted;
  
  /** The balises linked to the query word and the weight of the word in the document. */
  private Map<Balise, Map<QueryWord, Double>> balisesContainingQueryWord;
  
  /** The balises and their coefficient */
  private Map<Balise, Double> balisesCoefficient;

  /**
   * Instantiates a new query utility.
   *
   * @param documentsInserted the documents inserted
   */
  public QueryUtility(List<Document> documentsInserted)
  {
    this.documentsInserted = documentsInserted;
  }
  
  /**
   * Gets the relevent balises by calculating the cosinus ratio.<br>
   * <br><pre>
   *               SOMME(poids du mot de la requête) * SOMME(poids du mot en base + coefficient de balise)
   * ratio = --------------------------------------------------------------------------------------------------
   *         SQRT( SOMME(poids du mot de la requête^2) * SOMME((poids du mot en base + coefficient de balise)^2)
   * </pre>
   * @param query the query
   * @param maxNumberOfBalises the max number of balises to return
   * @return the relevent balises
   */
  public List<Balise> getReleventBalises(UserQuery query, int maxNumberOfBalises)
  {
    findBalisesContainingQueryWord(query.getQueryWords());
    
    HashMap<Balise, Double> balisesWithCoefficient = new HashMap<>();
    for (Balise b : balisesContainingQueryWord.keySet())
    {
      Map<QueryWord, Double> wordInBalise = balisesContainingQueryWord.get(b);
      double ratioNominator = 0.0;
      double sumQueryWordWeight = 0.0;
      double sumCoefficient = 0.0;

      Double baliseCoeff = balisesCoefficient.get(b);
      double doubleValue = baliseCoeff.doubleValue();
      
      for (QueryWord queryWord : wordInBalise.keySet())
      {
        double weight = queryWord.getWeight();
        Double tfIdf = wordInBalise.get(queryWord);
        ratioNominator += weight * (tfIdf + doubleValue);
        sumQueryWordWeight += Math.pow(weight, 2);
        sumCoefficient += Math.pow(tfIdf + doubleValue, 2);
      }
      
      double cosinusRatio = ratioNominator / Math.sqrt(sumQueryWordWeight*sumCoefficient);
      balisesWithCoefficient.put(b, cosinusRatio);
    }
    Map<Balise, Double> sortedBalisesWithCoefficient = MapUtil.sortByValue(balisesWithCoefficient);
    
    ArrayList<Balise> arrayList = new ArrayList<>(sortedBalisesWithCoefficient.keySet());
    return arrayList.size() > maxNumberOfBalises ? arrayList.subList(0, maxNumberOfBalises) : arrayList;
  }

  /**
   * Gets the balises containing query word.
   *
   * @param words the words
   * @return the balises containing query word
   */
  private void findBalisesContainingQueryWord(List<QueryWord> words)
  {
    balisesContainingQueryWord = new HashMap<>();
    balisesCoefficient = new HashMap<>();
    
    for (Document doc : documentsInserted)
    {
      List<Emplacement> emplacements = doc.getEmplacements();
      for (Emplacement emplacement : emplacements)
      {
        Mot terme = emplacement.getTerme();
        for (QueryWord queryWord : words)
        {
          if(terme.equals(queryWord.getWord()))
          {
            Balise balise = emplacement.getBalise();
            String nom = balise.getNom().toLowerCase();
            List<Balise> listOfParagraphes = null;
            switch (nom)
            {
              case "titre":
              case "date":
              case "auteur":
                // Get all the <p> in the document
                // Insert each <p> with the word
                // Insert each <p> with the coefficient 20
                listOfParagraphes = getChildrenParagraphes(balise.getDocument(), balise.getChemin());
                for (Balise balise2 : listOfParagraphes)
                {
                  insertBaliseAndWord(balise2, queryWord, emplacement.getPoids());
                  insertBaliseCoefficient(balise2, 20);
                }
              case "p" :
                insertBaliseAndWord(balise, queryWord, emplacement.getPoids());
                insertBaliseCoefficient(balise, 5.0);
                break;
              default:
                break;
            }
          }
        }
      }
    }
  }

  /**
   * Gets the list of paragraphes.
   *
   * @param document the document
   * @param chemin the chemin
   * @return the list of paragraphes
   */
  private List<Balise> getChildrenParagraphes(Document document, String chemin)
  {
    List<Balise> balises = new ArrayList<>();
    for (Balise b : document.getBalises())
    {
      String cheminBalise = b.getChemin() + b.getNom() + "[" + b.getBaliseNumber() + "]/";
      // chemin + nom + balise ID
      if(cheminBalise.contains(chemin) && b.getNom().equalsIgnoreCase("p"))
      {
        balises.add(b);
      }
    }
    return balises;
  }

  /**
   * Insert balise and coefficient (adds it if the coefficient already exists).
   *
   * @param balise the balise
   * @param i the i
   */
  private void insertBaliseCoefficient(Balise balise, double i)
  {
    if(!balisesCoefficient.containsKey(balise))
    {
      balisesCoefficient.put(balise, i);
    }
    else
    {
      Double coeff = balisesCoefficient.get(balise);
      balisesCoefficient.put(balise, coeff + i);
    }
  }

  /**
   * Insert balise and word.
   *
   * @param balise the balise
   * @param queryWord the query word
   * @param wordWeight the word weight
   */
  private void insertBaliseAndWord(Balise balise, QueryWord queryWord, double wordWeight)
  {
    if (!balisesContainingQueryWord.containsKey(balise))
    {
      balisesContainingQueryWord.put(balise, new HashMap<QueryWord, Double>());
    }

    Map<QueryWord, Double> list = balisesContainingQueryWord.get(balise);
    list.put(queryWord, wordWeight);
    balisesContainingQueryWord.put(balise, list);
  }
  
}
